﻿using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

// This namespace holds indicators in this folder and is required. Do not change it.
namespace uTrade.Core
{
    /// <summary>
    /// Directional Movement (DM). This is the same indicator as the ADX,
    /// with the addition of the two directional movement indicators +DI
    /// and -DI. +DI and -DI measure upward and downward momentum. A buy
    /// signal is generated when +DI crosses -DI to the upside.
    /// A sell signal is generated when -DI crosses +DI to the downside.
    /// </summary>
    public class DM : Indicator
    {
        private DataSeries dmPlus;
        private DataSeries dmMinus;
        private DataSeries sumDmPlus;
        private DataSeries sumDmMinus;
        private DataSeries sumTr;
        private DataSeries tr;

        internal DataSeries High, Low;
        private DataSeries Close;   //只在Close被修改时才会触发指标计算,以避免多个input造成指标计算多次的性能问题.

        protected override void Init()
        {
            Close = Input;

            dmPlus = new DataSeries(this.Input);
            dmMinus = new DataSeries(this.Input);
            sumDmPlus = new DataSeries(this.Input);
            sumDmMinus = new DataSeries(this.Input);
            sumTr = new DataSeries(this.Input);
            tr = new DataSeries(this.Input);
        }

        protected override void OnBarUpdate()
        {
            double high0 = High[0];
            double low0 = Low[0];
            double trueRange = high0 - low0;

            if (CurrentBar == 0)
            {
                tr[0] = trueRange;
                dmPlus[0] = 0;
                dmMinus[0] = 0;
                sumTr[0] = tr[0];
                sumDmPlus[0] = dmPlus[0];
                sumDmMinus[0] = dmMinus[0];
                Value[0] = 50;
            }
            else
            {
                double low1 = Low[1];
                double high1 = High[1];
                double close1 = Close[1];

                tr[0] = Math.Max(Math.Abs(low0 - close1), Math.Max(trueRange, Math.Abs(high0 - close1)));
                dmPlus[0] = high0 - high1 > low1 - low0 ? Math.Max(high0 - high1, 0) : 0;
                dmMinus[0] = low1 - low0 > high0 - high1 ? Math.Max(low1 - low0, 0) : 0;

                double sumDmPlus1 = sumDmPlus[1];
                double sumDmMinus1 = sumDmMinus[1];
                double sumTr1 = sumTr[1];

                if (CurrentBar < Period)
                {
                    sumTr[0] = sumTr1 + tr[0];
                    sumDmPlus[0] = sumDmPlus1 + dmPlus[0];
                    sumDmMinus[0] = sumDmMinus1 + dmMinus[0];
                }
                else
                {
                    sumTr[0] = sumTr1 - sumTr[1] / Period + tr[0];
                    sumDmPlus[0] = sumDmPlus1 - sumDmPlus1 / Period + dmPlus[0];
                    sumDmMinus[0] = sumDmMinus1 - sumDmMinus1 / Period + dmMinus[0];
                }

                double diPlus = 100 * (sumTr[0] == 0 ? 0 : sumDmPlus[0] / sumTr[0]);
                double diMinus = 100 * (sumTr[0] == 0 ? 0 : sumDmMinus[0] / sumTr[0]);
                double diff = Math.Abs(diPlus - diMinus);
                double sum = diPlus + diMinus;

                Value[0] = sum == 0 ? 50 : ((Period - 1) * Value[1] + 100 * diff / sum) / Period;
                DiPlus[0] = diPlus;
                DiMinus[0] = diMinus;
            }
        }

        #region Properties

        [Browsable(false)]
        public DataSeries DiPlus
        {
            get { return Values[1]; }
        }

        [Browsable(false)]
        public DataSeries DiMinus
        {
            get { return Values[2]; }
        }

        [Range(1, int.MaxValue)]
        [Parameter("Period")]
        public int Period { get; set; }

        #endregion Properties
    }

    #region generated code. Neither change nor remove.

    public partial class Indicator
    {
        private DM[] cacheDM;

        public DM DM(DataSeries high, DataSeries low, DataSeries close, int period)
        {
            var cat = cacheDM;
            if (cacheDM != null)
                for (int idx = 0; idx < cacheDM.Length; idx++)
                    if (cacheDM[idx] != null && cacheDM[idx].Period == period && cat[idx].High == high && cat[idx].Low == low && cacheDM[idx].EqualsInput(close))
                        return cacheDM[idx];
            return CacheIndicator<DM>(new DM() { Period = period, High = high, Low = low, Input = close }, ref cacheDM);
        }
    }

    public partial class Strategy
    {
        public DM DM(int period)
        {
            return DM(Datas[0], period);
        }

        public DM DM(TransDatas data, int period)
        {
            return Indicator.DM(data.High, data.Low, data.Close, period);
        }
    }
}

#endregion generated code. Neither change nor remove.